#/usr/bin/python27
#-*- coding: utf-8 -*-
#  Copyright 2011-2013 wuweihui@cn.ibm.com
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

#from Telnet import *
from CIMLogParser import *
from CLILogParser import *
from SSHClient import *
from config import *
import subprocess
import time, threading

class MSClient(object):
   def __init__(self, server=None, port=None, user=None, passwd=None):
      self.client = None
      self.cim_srv_is_set = False
      self.server = server or config.get('MS SMS', 'server')
      self.port = port or config.getint('MS SMS', 'port')
      self.user = user or config.get('MS SMS', 'user')
      self.passwd = passwd or config.get('MS SMS', 'passwd')
   
   def connect_to_win(self, timeout=120.0):
      if self.client:
         try:
            self.client.close_connection()
         except:
            pass
         finally:
            self.client = None
      if config.get('MS SMS', 'protocol').lower() == 'ssh':
          self.client = SSHWIN(self.server, self.port, timeout, newline='\r')
      else:
          self.client = WinTelnet(self.server, self.port, timeout, newline='\r\n')
      self.client.set_prompt(re.compile('[A-Za-z]\:\\\\[\w \\\\\-\.]*>'), 60)
      self.client.login(self.user, self.passwd)
      self.client.set_timeout(eval(config.get('Common', 'timeout')))
      self.cim_srv_is_set = False
      
   def close_connection(self):
      try:
          self.client.close_connection()
      except:
          pass
      self.cim_srv_is_set = False
      
   def exec_cmd(self, cmd, parser=None):
      if not self.client:
         self.connect_to_win()
      self.client.write(cmd)
      out = self.client.read_until_prompt()
      if parser:
         return parser(out)
      else:
         return out
      
   def exec_ms_cmd_ex(self, *args, **kws):
      cmdargs = []
      for arg in args:
         if not isinstance(arg, basestring):
            arg = arg.ObjectId
         cmdargs.append(arg)
      if kws.has_key('acc') and kws['acc'] == False:
         cmd = self._build_cmd(*cmdargs)
      else:
         cmd = self._build_cmd_with_acc(*cmdargs)
      if kws.has_key('parser'):
         parser = eval(kws['parser'])
      else:
         parser = cMSLogItem
      return self.exec_sms_cmd(cmd, parser)    

   def exec_sms_cmd(self, cmd, parser=None):
      if not self.client:
          self.connect_to_win()
      if not self.client.appmod:
          self.client.start_interapp(config.get('MS SMS', 'interapp'))
      if cmd in ["deleteReplicaList", "deleteStorageVolumeList"]:
         oldprompt = self.client.set_prompt(re.compile('number to remove: '), 20)
         out = ''
         try:
            out = self.exec_cmd(cmd, parser)
         finally:
            self.client.set_prompt(*oldprompt)
         return out
      return self.exec_cmd(cmd, parser)
   
   def _build_cmd(self, *args):
      return ' '.join([str(arg) for arg in args])
      
   def _build_cmd_with_acc(self, *args):
      return self._build_cmd(*args) + ' ' + config.get('CLI server', 'user') + ' ' + config.get('CLI server', 'passwd')
   
   def start_discovery(self, host=None, protocol='smis-cimxml', level=None, force=None, parser=cMSDiscoverItem):
      """
      sd or startDiscovery <host system><host-resource|smis-wmi|smis-cimxml> [[user name] [pwd|!]]
      level = 0, 1, 2
      force = True, False
      """
      host = host or config.get('CIM Agent', 'server') + ':' + config.get('CIM Agent', 'srvport')
      if level != None:
         self.exec_sms_cmd(self._build_cmd('discoveryLevel', level))
      if force != None:
         self.exec_sms_cmd(self._build_cmd('forcerediscovery', force and 'y' or 'n'))
      return self.exec_sms_cmd(self._build_cmd_with_acc('startDiscovery', host, protocol), parser)
   
   def wait_until_discovery_finish(self, timeout=120, parser=cMSDiscoverStatusItem):
      oldprompt = self.client.set_prompt(re.compile('discovery \{[\w\-]+\} \w+\; status \[\d+/.*\]', re.DOTALL), 100)
      oldtimeout = self.client.set_timeout(timeout)
      out = ''
      try:
         out = self.client.read_until_prompt()
      finally:
         self.client.set_prompt(*oldprompt)
         self.client.set_timeout(oldtimeout)
      if out and parser:
         return parser(out)
      return out
   
   def ms_enumerate(self, classname, filter='', parser=eMSLogItemList):   
      """
      e or enumerate [class name]
      ef or enumerateFilter <class name><string>
      """
      if not filter:
         return self.exec_sms_cmd(self._build_cmd('enumerate', classname), parser)
      else:
         return self.exec_sms_cmd(self._build_cmd('enumerateFilter', classname, filter.lower()!='all' and filter or ''), parser)
   
   def set_cim_server(self, server=None, namespace=None, user=None, passwd=None):
      """
      srv 9.186.96.201:5989 root/ibm admin adminadmin
      """      
      server = server or config.get('CIM Agent', 'server') + ':' + config.get('CIM Agent', 'srvport')
      namespace = namespace or config.get('CIM Agent', 'namespace')
      user = user or config.get('CLI server', 'user')
      passwd = passwd or config.get('CLI server', 'passwd')
      out = self.exec_sms_cmd(self._build_cmd('srv', server, namespace, user, passwd), parser=None)
      self.cim_srv_is_set = True
      return out
      
   def set_timeout(self, timeout):
      return self.client.set_timeout(timeout)      
   
   def ms_cim_enumerate(self, classname, filter=''):
      """
      enum <CLSNAME> <KW>
      """
      if not self.cim_srv_is_set:
         self.set_cim_server()
      return self.exec_sms_cmd(self._build_cmd('enum', classname, filter), parser=eMSLogItemList)


class CIMClient(object):
   def __init__(self, server=None, port=None, stype=None, user=None, passwd=None, keyfile=None):
      self.client = None   
      self.env_setup_flag = False
      self.port = port or config.getint('CIM Agent', 'port')
      self.server = server or config.get('CIM Agent', 'server')
      self.stype = stype or config.get('CIM Agent', 'servertype')
      self.user = user or config.get('CIM Agent', 'user')
      self.passwd = passwd or config.get('CIM Agent', 'passwd')
      self.keyfile = keyfile or config.get('CIM Agent', 'keyfile')

   def connect_to_cimagent(self, timeout=120):
      if self.client:
         try:
            self.client.close_connection()
         except:
            pass
      if config.get('CIM Agent', 'protocol').lower() == 'ssh':
          self.client = SSHCIMAgent(self.server, self.port, timeout, newline='\n')
          if not self.keyfile:
              self.client.login(self.user, self.passwd)
          else:
              self.client.login_with_public_key(self.user, self.keyfile, self.passwd)
      else:
          self.client = WinTelnet(self.server, self.port, timeout, newline='\r\n')
          self.client.login(self.user, self.passwd)
      self.client.set_prompt(re.compile(config.get('PROMPT', self.stype)+' '), 50)
      
      self.client.set_timeout(eval(config.get('Common', 'timeout')))
      
   def close_connection(self):
      try:
          self.client.close_connection()
      except:
          pass
      
   def exec_cmd(self, cmd, parser=None):
      if not self.client:
         self.connect_to_cimagent()
      self.client.write(cmd)
      out = self.client.read_until_prompt()
      if parser:
         return parser(out)
      else:
         return out
   
   def exec_cim_cmd(self, cmd, parser=None):
      if not self.client:
         self.connect_to_cimagent()
      if not self.env_setup_flag:
         self.env_setup()
      return self.exec_cmd(cmd, parser)

   def cim_enumerate(self, classname, filter='', parser=eCIMLogItemList):
      """
      CIMlib.exec_cim_cmd('cli201 gi \'IBMTSDS_StorageSystem.CreationClassName="IBMTSDS_StorageSystem",Name="IBM.2810-6000095"\'')
      """
      return self.exec_cim_cmd(self._build_cim_cmd('ei %s' % classname), parser)
   
   def env_setup(self):
      envcmd = 'self.' + config.get('CIM Agent', 'servertype').lower() + '_env_setup()'
      eval(envcmd)
      self.env_setup_flag = True
   
   def xiv_env_setup(self):
      """
      export XIVAGENT_HOME=/local/cim
      . $XIVAGENT_HOME/config/envConf
      """
      self.exec_cmd("export XIVAGENT_HOME=/local/cim")
      self.exec_cmd(". $XIVAGENT_HOME/config/envConf")
      
   def ds_env_setup(self):
      """
      . /opt/IBM/dsagent/config/envConf
      """
      self.su(config.get('CIM Agent', 'supass'))
      self.exec_cmd(". /opt/IBM/dsagent/config/envConf")
      
   def svc_env_setup(self):
      """
      export SVCAGENT_DATA=/data/cimom
      export SVCAGENT_HOME=/compass/cimom

      . $SVCAGENT_HOME/config/envConf 
      """
      self.exec_cmd("export SVCAGENT_DATA=/data/cimom")
      self.exec_cmd("export SVCAGENT_HOME=/compass/cimom")
      self.exec_cmd(". /compass/cimom/config/envConf")
   
   def su(self, password):
       self.client.write('')
       out = self.client.read_until_prompt()
       if out.strip().endswith('#'):
          return
       prompt = 'Password: '
       self.client.set_prompt(prompt, 15)
       self.client.write('su -')
       self.client.read_until_prompt()
       
       prompt = config.get('PROMPT', config.get('CIM Agent', 'servertype')).partition('@')[-1][:-1] + '# '
       self.client.set_prompt(re.compile(prompt), 50)
       self.client.write(password)
       ret = self.client.read_until_prompt()
       if ret.strip().endswith('>'):
           raise RuntimeError('su: Authentication Failure')
       
       #self.client.set_prompt(re.compile(config.get('PROMPT', config.get('CIM Agent', 'servertype'))+' '), 50)
       #self.client.set_prompt(prompt)
   
   def exit_su(self):
      self.client.write('')
      out = self.client.read_until_prompt()
      if out.strip().endswith('>'):
          return
      self.client.set_prompt(re.compile(config.get('PROMPT', config.get('CIM Agent', 'servertype'))+' '), 50)
      self.client.write('exit')
      self.client.read_until_prompt()
   
   def _build_cim_cmd(self, cmd, *args):
      """
      alias cli="cimcli -l 9.11.239.126:6989 -s -n root/ibm -u xxx -p xxx"
      """
      cimcmd = config.get('CIM Agent', 'cimcmd') + ' -l ' + config.get('CIM Agent', 'server') + ':' + config.get('CIM Agent', 'srvport') \
               + ' -s -n ' + config.get('CIM Agent', 'namespace') + ' -u ' + config.get('CLI server', 'user') \
               + ' -p ' + config.get('CLI server', 'passwd') + ' -niq ' + cmd + ' ' + ' '.join(args)
      return cimcmd
   
   def set_timeout(self, timeout):
      return self.client.set_timeout(timeout)
   
   def probe(self, logfile=None):
      clist = open('config/ibmclasslist.txt').readlines()
      if isinstance(logfile, basestring):
         logfile = open(logfile, 'wb')
      for c in clist:
         out = self.cim_enumerate(c.strip(), parser=None)
         if logfile:
            logfile.write(out)

class DSCLIClient(object):
   def __init__(self, server=None, user=None, passwd=None):
      self.server = server or config.get('CIM Agent', 'server')
      self.user = user or config.get('CLI server', 'user')
      self.passwd = passwd or config.get('CLI server', 'passwd')
   
   def _build_dscli(self, cmd):
      #local_cmd = ['help']
      dsclipath = config.get('CLI server', 'path')
      dscliprefix = " -hmc1 " + self.server \
                    + " -user " + self.user \
                    + " -passwd " + self.passwd + " "
      return '"' + dsclipath + '"' + dscliprefix + cmd
   
   def dscli(self, cmd, parser=None, timeout=None):
      """
      Some times dscli command won't return in command line. It's a command line defect.
      Please use dscli_without_wait instead
      """
      log("*WARN* dscli won't return some time, please use dscli_without_wait!")
      log("Execute command: " + cmd)
      debug_print(1, 'full command', self._build_dscli(cmd))
      p = subprocess.Popen(self._build_dscli(cmd), shell=True, bufsize=1024*1024, 
                           stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
      #######################
      # One defect in DSCLI client, help command will not return
      #######################
      if cmd.startswith('help '):
         state = 0
      else:
         state = self.wait_dscli(p, timeout)
      #out = p.stdout.read()
      out = unicode(p.stdout.read(), 'cp936').encode('utf-8')
      log(out)
      if state != 0 and state != 9:
         raise RuntimeError("System Command Failed! " + out)
      if state == 9:
         raise RuntimeError("System Command TimeOut! " + out)
      if parser:
         return parser(out)
      else:
         return out
      
   def dscli_without_wait(self, cmd, parser=None):
      log("Execute command: " + cmd)
      debug_print(1, 'full command', self._build_dscli(cmd))
      p = subprocess.Popen(self._build_dscli(cmd), shell=True, bufsize=1024*1024, 
                           stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
      
      out = unicode(p.stdout.read(), 'cp936').encode('utf-8')
      log(out)      
      if parser:
         return parser(out)
      else:
         return out
      
   def wait_dscli(self, process, timeout=None):
      if not timeout:
         return process.wait()
      st = time.time()
      self.state = None
      def _wait():
         self.state = process.wait()
      wp = threading.Thread(target=_wait, args=()).start()
      while time.time() - st < timeout and self.state == None:
         time.sleep(1)
      if self.state == None:
         self.state = 9
      return self.state
      

def get_all_dscli_help_msg():
   fp = open('config/dsclihelpinfo.txt', 'wb')
   dsclient = DSCLIClient()
   out = dsclient.dscli('help')
   commands = []
   for line in out.splitlines():
      commands.extend(line.split())
   for command in commands:
      out = dsclient.dscli('help %s' %command)
      print out
      fp.write('===Command Start===\n')
      fp.write(out)
      fp.write('===Command End===\n')

def get_dscli_cmd_help(cmd):
   fp = open('config/dsclihelpinfo.txt', 'rb')
   helpinfo = ''
   flag = False
   line = fp.readline()
   while line:
      if line.strip() == '===Command Start===':
         line = fp.readline()
         #print line
         if line and line.strip() == cmd:
            flag = True
      if flag and line.strip() == '===Command End===':
         fp.close()
         return helpinfo
      if flag:
         helpinfo += line
      line = fp.readline()
   fp.close()
   return helpinfo
      
def get_cmd_parameters_from_help_info(info):
   """
   return a dictionary value of parameters
   {'dev':       [False, 'Storage_Image_ID'],
    'rankgrp':   [True, '0','1']
   }
   False: this key is optional
   True:  this key is obligatory
   'Storage_Image_ID', '0','1': this is optional values or parameter name for this key
   """
   pardict = simpledict()
   #Get description
   re_par = re.compile('>>.*?<', re.DOTALL)
   ret = re_par.search(info)
   if not ret:
      return pardict
  
   desc = ret.group()
   #get the last line's option values
   lastpos = info.find('\n', ret.end())
   nextline = info[ret.end():info.find('\n', lastpos+1)]
   while nextline.strip():
      desc += nextline
      lastpos = lastpos + len(nextline)
      nextline = info[lastpos:info.find('\n', lastpos+1)]
    
   #Get command name   
   re_c = re.compile('>>\-+\s*(\w+)\-')
   cm = re_c.match(desc)
   cmd = cm.group(1)
   desc = '-'*len(cm.group()) + desc[cm.end():]
   desc = desc.replace('"-"', '@')
   debug_print(2, desc)
   
   #Get Parameters
   lines = desc.splitlines()
   i = 0
   while i < len(lines):
      pd, dp = _process_desc(lines, i)
      pardict.update(pd)
      i = dp + 1
   
   return pardict


def _process_desc(lines, li, si=0, ei=None):
   """
   return parameters in diction value and the last line number processed in this option
   return pardict, depth
   pardict:
   {'dev':       [False, 'Storage_Image_ID'],
    'rankgrp':   [True, '0','1']
   }
   """
   pardict = simpledict()
   sections = []
   re_a = re.compile('\+\-.*?\-\+|\-\s+\-\w+\-|\-\s*[^\-\+\s]+\-')
   re_o = re.compile('\+\-\-+-\+')
   re_k = re.compile('\-\s+\-(\w+)\-')
   re_v = re.compile('\-\s*([^\-\+\s]+)\-')
   
   def get_subs(re_s, line):
      subs = []
      sm = re_s.search(line)
      while sm:
         subs.append(sm)
         sm = re_s.search(line, sm.end())
      return subs
   if ei:
      line = lines[li][si:ei]
      #some times the description length no matched
      n = 0
      while line[-2] != '-' and ei+n < len(lines[li]):
         line += lines[li][ei+n]
         n += 1
   else:
      line = lines[li][si:]
   ams = get_subs(re_a, line)
   k = 'main'
   i = 0
   depth = li
   for am in ams:
      sub = am.group()
      debug_print(3, 'processing sub line:', sub)
      if sub.startswith('+'):
         debug_print(2, 'option line', sub)
         #process first line
         tpd, tdp = _process_desc(lines, li, am.start()+si+1, am.end()+si-1)
         if tpd.has_key('main'):
               if k == 'main' and not pardict.has_key(k):
                  pardict[k] = [False]
               pardict[k].extend(tpd['main'][1:])
               tpd.pop('main')
               if k == 'main':
                  pardict[k] = [False] + pardict[k][1:]
         if tpd:
            for tk, tv in tpd.items():
               pardict[tk] = [False] + tv[1:]
         depth = max(depth, tdp)
         n = 1
         #One risk here, if the second line is on the top of the first line 
         #and the second line is also a option line, it will be dead loop here.
         if li+n >= len(lines):
            n = -n
         nextline = lines[li+n][am.start()+si:am.end()+si]
         debug_print(3, 'nextline', nextline)
         if not nextline.strip() and n > 0:
            nextline = lines[li-n][am.start()+si:am.end()+si]
            n = -n
         if not nextline.strip():
            continue
         
         tpd, tdp = _process_desc(lines, li+n, am.start()+si, am.end()+si)
         
         if tpd.has_key('main'):
            if k == 'main' and not pardict.has_key(k):
               pardict[k] = [False]
            pardict[k].extend(tpd['main'][1:])
            tpd.pop('main')
            if k == 'main':
               pardict[k] = [False] + pardict[k][1:]
         if tpd:
            for tk, tv in tpd.items():
               pardict[tk] = [False] + tv[1:]
         
         depth = max(depth, tdp)
         
      elif re_k.search(sub):
         debug_print(2, 'key line', sub)
         k = re_k.search(sub).group(1)
         pardict[k] = [True]
      elif re_v.search(sub):
         debug_print(2, 'v line', sub)
         v = re_v.search(sub).group(1)
         if not pardict.has_key(k):
            pardict[k] = [True, v]
         else:
            pardict[k].append(v)
         
   return pardict, depth

if __name__ == '__main__':
   #test_cimagent()
   test_winssh()
   pass
   

